package xyz.naokeziteng.level2cache.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.naokeziteng.level2cache.annotations.Level2Cacheable;
import xyz.naokeziteng.level2cache.service.DistributedLock;
import xyz.naokeziteng.level2cache.service.L1CacheService;
import xyz.naokeziteng.level2cache.service.L2CacheService;
import xyz.naokeziteng.level2cache.util.SpringELUtils;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @author hawk
 * @date 2022/8/2
 * @desc 缓存查询切面
 **/
@Slf4j
@Aspect
@Component
public class CacheableAspect {
    @Autowired
    private L1CacheService l1CacheService;
    @Autowired
    private L2CacheService l2CacheService;
    @Autowired
    private DistributedLock distributedLock;

    @Pointcut("@annotation(xyz.naokeziteng.level2cache.annotations.Level2Cacheable)")
    public void cacheable() {
    }

    @Around("cacheable()")
    public Object aroundCacheable(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String lockKey = "LOCK_KEY:";
        boolean l1l2Lock = false;
        try {
            Level2Cacheable cacheable = method.getAnnotation(Level2Cacheable.class);
            //获取缓存完整key
            String key = SpringELUtils.parseExpression(cacheable.key(), method, joinPoint.getArgs());
            String completeKey = cacheable.cacheName() + ":" + key;
            //判断l1缓存中是否存在
            Object result = l1CacheService.get(completeKey);
            if (Objects.nonNull(result)) {
                log.debug("l1缓存命中, key: {}", completeKey);
                return result;
            }
            result = l2CacheService.get(completeKey);
            if (Objects.nonNull(result)) {
                log.debug("l2缓存命中, key: {}", completeKey);
                l1l2Lock = distributedLock.tryLock(completeKey, 60);
                if (l1l2Lock) {
                    log.debug("l2缓存同步至l1");
                    l1CacheService.cacheUpdate(completeKey);
                }
                return result;
            }
            log.debug("l1,l2缓存未命中，开始查询数据库");
            result = joinPoint.proceed();
            l1l2Lock = distributedLock.tryLock(completeKey, 60);
            if (l1l2Lock) {
                log.debug("l2缓存更新");
                l2CacheService.set(completeKey, result, cacheable.l2Timeout());
                log.debug("l1缓存更新");
                l1CacheService.cacheUpdate(completeKey);
            }
            return result;
        } catch (Exception e) {
            log.error("缓存查询失败, {}", e.getMessage());
            throw new RuntimeException("缓存查询失败, " + e.getMessage());
        } finally {
            //释放分布式锁
            if (l1l2Lock) {
                distributedLock.releaseLock(lockKey);
            }
        }
    }

}
